"""
Gateway for hft
"""

import time
from collections import defaultdict

from strategylib.services.bbc_strategy_template import BBCStrategyTemplate
from strategylib.models.bbc_mds_data import Depth, InstrumentInfo, Orderbook, Trades
from strategylib.models.bbc_oms_data import BBCBalance, BBCOrderTrade
from strategylib.models.bbc_common_enum import ExchangeType, InstType
from strategylib.models.bbc_common_enum import ApiSource as bbcApiSource
from strategylib.models.bbc_oms_enum import OffsetFlag, OrderStatus, TimeInForce
from strategylib.models.bbc_oms_enum import Direction as bbcDirection
from strategylib.models.bbc_oms_enum import OrderType as bbcOrderType
import traceback
import json

from vn_local.trader.gateway import BaseGateway
from vn_local.trader.constant import (
    BinanceContractType, Direction,
    Exchange, Product,
    Status, OrderType,
    Interval, Offset, ApiSource
)
from vn_local.trader.object import (
    LogData,
    TickData,
    OrderTradeData,
    AccountData,
    PublicTradeData,
    BinanceContractData,
    PositionData,
    BarData,
    QuoteData,
    OrderRequest,
    CancelRequest,
    AmendRequest,
    SubscribeRequest,
    HistoryRequest,
    QuoteRequest,
    ContractData,
    CancelAllRequest,
    QueryOpenOrderRequest
)

from vn_local.trader.util_logger import setup_logger
from vn_local.trader.utility import get_folder_path, virtual, get_file_path, Record
from vn_local.event import Event, EventEngine
from vn_local.trader.event import (
    EVENT_BAR,
    EVENT_FUND_RATE_DATA,
    EVENT_QUOTE,
    EVENT_TICK,
    EVENT_ORDER,
    EVENT_TRADE,
    EVENT_POSITION,
    EVENT_ACCOUNT,
    EVENT_CONTRACT,
    EVENT_LOG
)

from typing import Any, Callable, Dict, List, Tuple, Optional
from copy import copy
from threading import Thread
from datetime import datetime
from vn_local.trader.event import EVENT_LOG
from dataclasses import dataclass
from dataclasses_json import dataclass_json

ContractType_BBC2VT: Dict[str, BinanceContractType] = {
    "USDT_SWAP": BinanceContractType.PERPETUAL
}
# InstType映射(smc需要str，下单需要InstType)
InstType_STR2BBC: Dict[str, InstType] = {
    "SPOT": InstType.SPOT,
    "USDT_SWAP": InstType.USDT_SWAP,

}
InstType_BBC2STR: Dict = {v: k for k, v in InstType_STR2BBC.items()}
# 交易所类型映射
Exchange_VT2BBC = {
    Exchange.OKX: Exchange.OKX,
    Exchange.BINANCE: Exchange.BINANCE,
    Exchange.BYBIT: ExchangeType.BYBIT,
    Exchange.GATEIO: ExchangeType.GATEIO,
    Exchange.BTSE: ExchangeType.BTSE,
    Exchange.UNISWAP: ExchangeType.UNISWAP,
    Exchange.UNDEFINED: ExchangeType.UNDEFINED
}
Exchange_BBC2VT = {v: k for k, v in Exchange_VT2BBC.items()}
Exchange_STR2BBC = {
    "OKX": Exchange.OKX,
    "BINANCE": Exchange.BINANCE,
    "BYBIT": ExchangeType.BYBIT,
    "GATEIO": ExchangeType.GATEIO,
    "BTSE": ExchangeType.BTSE,
    "UNISWAP": Exchange.UNISWAP,
    "UNDEFINED": Exchange.UNDEFINED

}
# Offset类型映射
Offset_VT2BBC = {
    Offset.OPEN: OffsetFlag.OPEN,
    Offset.CLOSE: OffsetFlag.CLOSE,
    Offset.NONE: OffsetFlag.UNDEFINED
}
Offset_BBC2VT = {v: k for k, v in Offset_VT2BBC.items()}

# Direction类型映射
Direction_VT2BBC = {
    Direction.LONG: bbcDirection.BUY,
    Direction.SHORT: bbcDirection.SELL,
    Direction.UNDEFINED: bbcDirection.UNDEFINED
}
Direction_BBC2VT = {v: k for k, v in Direction_VT2BBC.items()}
Pos_Adj_HFT2VT = {
    bbcDirection.SELL: -1,
    bbcDirection.BUY: 1
}
# Ordertype类型映射
OrderType_VT2BBC = {
    OrderType.LIMIT: bbcOrderType.LIMIT,
    OrderType.MAKER: bbcOrderType.LIMIT,
    OrderType.MARKET: bbcOrderType.MARKET,
    OrderType.FOK: bbcOrderType.FOK,
    OrderType.FAK: bbcOrderType.IOC,
    OrderType.ERROR: bbcOrderType.UNDEFINED
}
OrderType_BBC2VT = {v: k for k, v in OrderType_VT2BBC.items()}

# Product类型映射
ProductType_VT2BBC = {
    Product.SPOT: InstType.SPOT,
    Product.USWAP: InstType.USDT_SWAP
}
ProductType_BBC2VT = {v: k for k, v in ProductType_VT2BBC.items()}

# Status类型映射
Status_BBC2VT = {
    OrderStatus.PENDING_NEW: Status.PENDING,
    OrderStatus.NEW: Status.NOTTRADED,
    OrderStatus.PART_FILLED: Status.PARTTRADED,
    OrderStatus.FILLED: Status.ALLTRADED,
    OrderStatus.REJECTED: Status.REJECTED,
    OrderStatus.CANCELLING: Status.CANCELLING,
    OrderStatus.CANCELLED: Status.CANCELLED,
    OrderStatus.UNKNOWN: Status.UNKNOWN,
    OrderStatus.FAILED: Status.FAILED
}

# APISource类型映射
ApiSource_BBC2VT = {
    bbcApiSource.ADD_NEW_ORDER: ApiSource.SEND,
    bbcApiSource.CANCEL_ORDER: ApiSource.CANCEL,
    bbcApiSource.QUERY_ORDER: ApiSource.QUERY,
    bbcApiSource.REST: ApiSource.REST,
    bbcApiSource.WEBSOCKET: ApiSource.WEBSOCKET,
    bbcApiSource.QUERY_OPEN_ORDER: ApiSource.QUERYOPEN,
    bbcApiSource.UNDEFINED: ApiSource.ERROR
}

# 合约数据全局缓存字典
symbol_name_map: Dict[str, str] = {}
vt_symbol_origin_map: Dict[str, int] = {}
contract_type_map: Dict[str, BinanceContractType] = {}
margin_asset_map: Dict[str, str] = {}
last_price_map: Dict[str, float] = {}
contract_map: Dict[str, BinanceContractData] = {}


class BBCConverter:
    """
    回调接受的结构体转成vt结构体
    transfer python-lib/object/object/Order to OrderData
    """

    def __init__(self, gateway_name: str = 'BBC'):
        self.gateway_name = gateway_name

    def convert_btse_contract(self, bbc_contract: InstrumentInfo) -> ContractData:
        contract = ContractData(
            symbol=bbc_contract.inst_id,
            exchange=Exchange.BTSE,
            name=bbc_contract.vids,
            base=bbc_contract.base,  # 适配高频
            quote=bbc_contract.quote,  # 适配高频
            pricetick=bbc_contract.tick_size,
            size=bbc_contract.value,  # 1张表示多少个币
            min_volume=bbc_contract.min_size,
            product=Product.SPOT if bbc_contract.inst_type == InstType.SPOT else Product.FUTURES,
            gateway_name=self.gateway_name,
            history_data=True
        )
        return contract

    def convert_account(self, bbc_balance: BBCBalance) -> AccountData:
        account = AccountData(
            exchange=Exchange_BBC2VT[bbc_balance.exchange_type],
            currency=bbc_balance.currency,
            available=bbc_balance.available,
            frozen=bbc_balance.frozen,
            pnl=bbc_balance.unrealized_pnl,
            total=bbc_balance.total,
            update_time=bbc_balance.update_time,
            # api_source=ApiSource_BBC2VT[bbc_balance.apiSourceEnum],
            gateway_name=self.gateway_name
        )
        return account

    def convert_order_trade(self, bbc_order_trade: BBCOrderTrade) -> OrderTradeData:

        order_trade = OrderTradeData(
            symbol=bbc_order_trade.inst_id,
            exchange=Exchange_BBC2VT[bbc_order_trade.exchange_type],
            orderid=str(bbc_order_trade.strategy_ord_id),  # # 只有自定义的订单号返回
            type=OrderType_BBC2VT[bbc_order_trade.order_type],
            direction=Direction_BBC2VT[bbc_order_trade.direction],
            offset=Offset_BBC2VT[bbc_order_trade.offset_flag],
            price=bbc_order_trade.price,
            volume=bbc_order_trade.volume,
            traded=bbc_order_trade.traded_volume,
            trade_price=bbc_order_trade.trade_price,
            trade_diff=None,
            status=Status_BBC2VT[bbc_order_trade.order_status],
            exchange_status=Status_BBC2VT[bbc_order_trade.oms_status],
            insert=(bbc_order_trade.oms_ts * 0.001),
            update=(bbc_order_trade.exch_ts * 0.001),
            api_source=ApiSource_BBC2VT[bbc_order_trade.api_source],
            is_maker=bbc_order_trade.maker_flag,
            reduce_only=bbc_order_trade.reduce_only,
            error_id=bbc_order_trade.err_id,
            error_msg=bbc_order_trade.origin_msg,
            reference=bbc_order_trade.strategy_ref,
            gateway_name=self.gateway_name
        )

        if bbc_order_trade.api_source == bbcApiSource.QUERY_OPEN_ORDER:
            order_trade.error_id = bbc_order_trade.data_len

        return order_trade


def generate_order_id():
    return str(time.time_ns())


class BBCGateway(BaseGateway):
    """
    Run as BaseGateway
    """

    default_setting: Dict[str, Any] = {
        "inst_type": "",
        'subscribe_codes': [],
        'balance_codes': [],
        "trader": "",
        'strategyId': "",
        "accountId": "",
        "bbc_config_path": ""
    }
    exchanges: List[Exchange] = [Exchange.BTSE]

    def __init__(self, event_engine: EventEngine):
        # Constructor
        super().__init__(event_engine, gateway_name="BBC")

        self.bbc_converter = None
        self.bbc = None
        # self.hft_thread: Thread = Thread(target=self.run)
        # mktdata
        self.ticks: Dict[int, TickData] = {}
        self.public_trades: Dict[int, PublicTradeData] = {}
        self.replace_times: Dict[int, int] = defaultdict(int)
        self.replace_limit = 3

        # 监控行情计时器
        # self.count_init = False
        self.send_count = 0
        self.receive_count = 0
        self.effective_count = 0
        self.effective_net_delay_tolerant = 75500
        self.effective_dequee_delay_tolerant = 2000

        self.tick_count = 0
        self.trade_count = 0
        self.monitor_count = 0

        self.tick_record_threshold = 100
        self.trade_record_threshold = 100
        self.monitor_record_threshold = 100

    def connect(self, setting: Dict) -> None:
        #
        self.inst_type = InstType_STR2BBC[setting["inst_type"]]
        self.subscribe_codes = setting["subscribe_codes"]
        self.balance_codes = setting["balance_codes"]
        self.trader = setting["trader"]
        self.account_id = setting["accountId"]
        self.strategy_id = setting["strategyId"]
        self.bbc_config_path = setting["bbc_config_path"]
        self.tick_len = 1
        self.coin_list = list(set([x.split('.')[2] for x in self.subscribe_codes]))

        with open(self.bbc_config_path, 'r') as f:
            bbc_conf = json.load(f)
        self.bbc = BBCStrategyTemplate(bbc_conf)
        self.bbc.set_balance_cb(self.on_balance)
        self.bbc.set_order_trade_cb(self.on_order_trade)
        self.bbc.set_on_depth1_cb(self.on_depth1)
        # self.bbc.set_on_orderbook_cb(self.on_orderbook)
        self.bbc.set_on_public_trades_cb(self.on_public_trades)
        # self.bbc.set_on_strategy_config_cb(self.on_strategy_config)
        self.bbc.set_on_scheduler_notify_cb(self.on_scheduler_notify)

        # converter
        self.bbc_converter = BBCConverter()
        # init_query
        self.query_contract()
        # self.query_position()
        self.query_account()

        # # 启动线程
        # self.hft_thread.start()

    def subscribe(self, req: SubscribeRequest) -> None:
        if req.vt_symbol not in vt_symbol_origin_map:
            # # 保证查询到了contract
            self.write_log(f"找不到该合约代码{req.vt_symbol}")
            return
        tick = TickData(
            symbol=req.symbol,
            name=vt_symbol_origin_map.get(req.vt_symbol, ""),
            exchange=req.exchange,
            datetime=datetime.now(),
            gateway_name=self.gateway_name,
        )

        public_trade = PublicTradeData(
            symbol=req.symbol,
            name=vt_symbol_origin_map.get(req.vt_symbol, ""),
            exchange=req.exchange,
            datetime=datetime.now(),
            gateway_name=self.gateway_name,
        )
        # print(tick)
        self.ticks[tick.name] = tick
        self.public_trades[tick.name] = public_trade

    def on_depth1(self, depth1: Depth):
        # data_str = depth1.get_string()
        # print(f'[on_depth1] depth1: {data_str}')

        if depth1.inst_id not in self.coin_list:
            return

        if self.event_engine._queue.qsize() > 5000:
            return

        tick = self.ticks.get(depth1.vids)

        # 未初始化过滤
        if not tick:
            print(f'[on_depth1]: not subscribed yet')
            return

        # 乱序过滤
        if depth1.exch_ts * 0.001 < tick.ts:
            return

        last_tick = copy(tick)

        for n in range(self.tick_len):
            b_price = depth1.bp[n]
            b_volume = depth1.bv[n]
            a_price = depth1.ap[n]
            a_volume = depth1.av[n]
            tick.__setattr__("bid_price_" + str(n + 1), float(b_price))
            tick.__setattr__("bid_volume_" + str(n + 1), float(b_volume))
            tick.__setattr__("ask_price_" + str(n + 1), float(a_price))
            tick.__setattr__("ask_volume_" + str(n + 1), float(a_volume))

        tick.ts = int(depth1.exch_ts * 0.001)
        tick.tsNet = int(depth1.received_ts * 0.001)
        tick.tsParse = int(depth1.client_received_ts * 0.001)
        tick.tsGateway = int(time.time() * 1e6)

        change = False
        for n in range(self.tick_len):
            if last_tick.__getattribute__(f"bid_price_{(n + 1)}") != tick.__getattribute__(
                    f"bid_price_{(n + 1)}"):
                change = True
                break
            if last_tick.__getattribute__(f"bid_volume_{(n + 1)}") != tick.__getattribute__(
                    f"bid_volume_{(n + 1)}"):
                change = True
                break
            if last_tick.__getattribute__(f"ask_price_{(n + 1)}") != tick.__getattribute__(
                    f"ask_price_{(n + 1)}"):
                change = True
                break
            if last_tick.__getattribute__(f"ask_volume_{(n + 1)}") != tick.__getattribute__(
                    f"ask_volume_{(n + 1)}"):
                change = True
                break

        if change:
            self.receive_count += 1
            self.on_tick(copy(tick))

            if self.tick_count >= self.tick_record_threshold:
                data_str = depth1.get_string()
                self.write_log(
                    f"[on_bbc_gateway:tick] = {data_str}")
                log = LogData(gateway_name="bbc", msg=f"[on_gateway:tick] = {data_str}", level=40, prefix="bbc")
                self.on_log(log)
                self.tick_count = 0

        if (tick.tsNet - tick.ts > self.effective_net_delay_tolerant) or (
                tick.tsGateway - tick.tsNet > self.effective_dequee_delay_tolerant):
            return
        self.effective_count += 1

    # def on_orderbook(self, orderbook: Orderbook):
    #     data_str = orderbook.get_string()
    #     print(f'[on_orderbook] orderbook: {data_str}')
    #     pass

    def on_public_trades(self, public_trade: Trades):
        # data_str = public_trade.get_string()
        # print(f'[on_public_trade] trade: {data_str}')

        pt = self.public_trades.get(public_trade.vids)

        if not pt:
            return

        if public_trade.exch_ts * 0.001 < pt.ts:
            return

        pt.volume = public_trade.sz
        pt.price = public_trade.px
        pt.direction = Direction.LONG if public_trade.side == bbcDirection.BUY else Direction.SHORT

        pt.ts = int(public_trade.exch_ts * 0.001)
        pt.tsNet = int(public_trade.received_ts * 0.001)
        pt.tsPrice = int(public_trade.client_received_ts * 0.001)
        pt.tsGateway = int(time.time() * 1e6)

        self.on_public_trade(pt)

        if self.trade_count >= self.trade_record_threshold:
            data_str = public_trade.get_string()
            self.write_log(
                f"[on_bbc_gateway:public_trade] = {data_str}")
            log = LogData(gateway_name="bbc", msg=f"[on_gateway:public_trade] = {data_str}", level=40, prefix="bbc")
            self.on_log(log)
            self.trade_count = 0

    def on_balance(self, bbc_balance: BBCBalance):
        if bbc_balance.currency not in self.balance_codes:
            return

        if bbc_balance.inst_type != self.inst_type:
            return

        account = self.bbc_converter.convert_account(bbc_balance)
        self.on_account(account)

    def close(self):
        pass

    def query_account(self):
        for exchange in self.exchanges:
            for currency in self.balance_codes:
                bbc_balance = self.bbc.get_balance_info(Exchange_VT2BBC[exchange], InstType.SPOT, self.account_id,
                                                        currency)
                account = self.bbc_converter.convert_account(bbc_balance)
                self.on_account(account)

    def query_contract(self):
        # print(self.exchanges)
        # print(self.coin_list)
        for exchange in self.exchanges:
            for instrument in self.coin_list:
                # print(Exchange_VT2BBC[exchange], self.inst_type, instrument)
                instrument_info = self.bbc.get_instrument_info(Exchange_VT2BBC[exchange], self.inst_type, instrument)
                # print(instrument_info)
                contract = self.bbc_converter.convert_btse_contract(instrument_info)
                # print(contract)
                vt_symbol_origin_map[contract.vt_symbol] = contract.name
                self.on_contract(contract)

    def query_smc(self, exchange, inst_type, symbol):
        instrument_info = self.bbc.get_instrument_info(exchange, inst_type, symbol)
        # print(instrument_info)
        return instrument_info

    def query_position(self) -> None:
        pass

    def send_order(self, req: OrderRequest):
        orderid = generate_order_id()
        order = req.create_order_data(orderid, self.gateway_name)
        post_only = True if req.type == OrderType.MAKER else False
        try:
            self.bbc.add_new_order(Exchange_VT2BBC[req.exchange], self.trader, self.account_id, self.inst_type,
                                   req.symbol,
                                   Direction_VT2BBC[req.direction], OrderType_VT2BBC[req.type], TimeInForce.GTC,
                                   req.price, req.volume, req.reference, orderid, self.strategy_id, post_only,
                                   req.reduce_only, 100)
            # print([Exchange_VT2BBC[req.exchange], self.trader, self.account_id, self.inst_type, req.symbol,
            #        Direction_VT2BBC[req.direction], OrderType_VT2BBC[req.type], TimeInForce.GTC,
            #        req.price, req.volume, req.reference, orderid, self.strategy_id, post_only,
            #        req.reduce_only], prefix="bbc")
            return order.vt_orderid
        except Exception as e:
            traceback.print_exception()
            # self.write_log([Exchange_VT2BBC[req.exchange], self.trader, self.account_id, self.inst_type, req.symbol,
            #        Direction_VT2BBC[req.direction], OrderType_VT2BBC[req.type], TimeInForce.GTC,
            #        req.price, req.volume, req.reference, orderid, self.strategy_id, post_only,
            #        req.reduce_only], prefix="bbc")

    def query_order(self, req: CancelRequest):
        self.bbc.query_order(Exchange_VT2BBC[req.exchange], self.inst_type, self.account_id, self.trader, "", req.orderid)

    def cancel_all_orders(self, req: CancelAllRequest):
        self.bbc.cancel_all_by_symbol(Exchange_VT2BBC[req.exchange], ProductType_VT2BBC[req.product],
                                      self.account_id, req.symbol)

    def cancel_order(self, req: CancelRequest):
        self.bbc.cancel_order(Exchange_VT2BBC[req.exchange], self.inst_type, self.account_id, self.trader, "", req.orderid)

    def deal_with_no_open_orders(self, bbc_order_trade):
        bbc_order_trade.account_id = self.account_id
        bbc_order_trade.strategy_id = self.strategy_id
        bbc_order_trade.inst_id = self.coin_list[0]
        return bbc_order_trade

    def on_order_trade(self, bbc_order_trade: BBCOrderTrade):
        # data_str = bbc_order_trade.get_string()
        # print(f'[on_order_trade]: {data_str}')
        if (Exchange_BBC2VT[bbc_order_trade.exchange_type] == Exchange.UNDEFINED and
                ApiSource_BBC2VT[bbc_order_trade.api_source] == ApiSource.QUERYOPEN):
            bbc_order_trade = self.deal_with_no_open_orders(bbc_order_trade)

        if bbc_order_trade.strategy_id != self.strategy_id:
            return

        if bbc_order_trade.inst_id not in self.coin_list:
            return

        order_trade = self.bbc_converter.convert_order_trade(bbc_order_trade)
        self.on_ordertrade(order_trade)
        # print(order_trade)

    def get_spot_order_book(self, symbol: str, exchange: str, full_book=False) -> Tuple:
        # print("get_spot_order_book", symbol,exchange)

        # ok, key, data = self.databox.get_md(exchange, self.inst_type_str, "DEPTH20", symbol)
        # return data["asksList"], data["bidsList"] if ok else None

        ob = self.bbc.get_orderbook(Exchange_STR2BBC[exchange], self.inst_type, symbol)
        return ob.ap, ob.bp if ob else None if not full_book else ob.ap, ob.bp, ob.av, ob.bv

    def get_bbo(self, symbol: str, exchange: str) -> Tuple:
        # if symbol == "BTSE-ETH":
        #     return [1], [1]
        bbo = self.bbc.get_depth1(Exchange_STR2BBC[exchange], self.inst_type, symbol)
        return bbo.ap, bbo.bp if bbo else None

    def get_trades(self, symbol: str, exchange: str) -> Tuple:
        trade = self.bbc.get_trades(Exchange_STR2BBC[exchange], self.inst_type, symbol)
        return trade.px if trade else None

    def on_scheduler_notify(self, scheduler_notify):
        # print(scheduler_notify)
        # print(f"[on_scheduler_notify]: {datetime.now()}")
        now = int(time.time())
        self.on_timer(now)
        self.monitor_count += 1
        self.tick_count += 1
        self.trade_count += 1

        if self.monitor_count >= self.monitor_record_threshold:
            self.write_log(
                f"{time.time()} {self.coin_list[0]} " \
                f"sendCount: {self.send_count}, receive_Count: {self.receive_count}, low-latencyCount: {self.effective_count} " \
                f"event queue left size: {self.event_engine._queue.qsize()}")
            # self.write_log(
            #     f"{time.time()} {self.coin_list[0]} sendCount: {self.send_count}, receive_Count: {self.receive_count}, low-latencyCount: {self.effective_count} " \
            #     f"event queue left size: {self.event_engine._queue.qsize()}")
            self.monitor_count = 0

    def query_open_orders(self, query_all_request: QueryOpenOrderRequest):
        self.bbc.query_open_order(exchange_type=Exchange_VT2BBC[query_all_request.exchange],
                                  inst_type=ProductType_VT2BBC[query_all_request.product],
                                  account=self.account_id,
                                  inst_id=query_all_request.symbol)
